Keys

Keys overview

Key represents a set of identity fields. Every hierarchy defines its own identity structure and every entity is identified by key. Number of fields in key is limited to 8. DataObjects.Net supports the following .NET types to be used in identity fields:

  • boolean;
  • byte, sbyte, short, ushort, int, uint, long, ulong;
  • string, char;
  • double, float, decimal;
  • Guid, DateTime, TimeSpan;
  • Reference to Entity

Usage of Structure and EntitySet<T> types is not allowed.

Number and types of identity fields are set once for all persistent hierarchy, so all descendants of hierarchy root share the key structure. In order to set up identity fields, KeyAttribute class should be used:

[HierarchyRoot]
public class Document : Entity
{
  [Key, Field]
  public int Id { get; private set; }

  ...
}

KeyAttribute must be placed on each identity field. Also note, that identity field must be immutable, it is prohibited for identity field to have a setter other than private.

For persistent types with complex keys (which have more than one identity field) explicit identity field order is strictly recommended, because the particular order of list of properties, got with the help of .NET reflection, is not guaranteed at all.

[HierarchyRoot]
public class BookReview : Entity
{
  [Field, Key(0)]
  public Book Book { get; private set; }

  [Field, Key(1)]
  public Person Reviewer { get; private set; }

  [Field(Length = 4096)]
  public string Text { get; set; }

  public BookReview(Session session, Book book, Person reviewer)
    : base(session, book, reviewer)
  {}
}

Pay attention to KeyAttribute usage. In this example it also used to set the position of identity field within complex key. So for BookReview type the structure of key is {Book, Person}. Also note that values for both identity fields are required in BookReview constructor and passed to the base constructor of Entity where actual key is constructed.

Working with keys

Creating keys

Creating the next unique key in corresponding key sequence for the specified persistent type. This can be done by these group of static methods:

Key.Generate<Document>(Session);
// or
Key.Generate(Session, typeof (Document));

Note that methods don’t receive any arguments except Session & Type. This means that we ask to generate the next unique key in key sequence.

It is also worth noting that generation of next key in sequence requires an instanse of Session because DataObjects.Net might need to connect to underlying storage and call the corresponding key generator for a next key in sequence.

For example, to fetch an entity from storage we need a key that identifies it. In this case we need to construct a key with already known value(s).

// Active session is not required
Key.Create<Document>(Domain, params object[] values);
Key.Create(Domain, typeof (Document), params object[] values);

If we want to build a key for an instance of Document class with identifier equals to 25 we write something like this:

var key = Key.Create<Document>(Domain, 25);

Note that presence of Session is not required as we don’t need a connection to underlying storage at all.

There is no public constructor in Key class. The reason for this and the usage of Factory method pattern instead of constructors is that we have several Key implementations, 4 of them are designed and extremely optimized for short keys (Key<T1>, Key<T1,T2>, Key<T1,T2,T3>, Key<T1,T2,T3,T4>) and one is for keys with length up to 8 fields (LongKey).

Serializing and deserializing keys

Keys can be automatically serialized into the corresponding string representation with the help of Key.Format() method and deserialized from the string using static Key.Parse() method.

var key = Key.Create<Document>(25);
var str = key.Format();
Console.WriteLine(str);
// This will print: "103:25"
// where 103 is identifier of Document type in Domain model
// and 25 is the value of identifier field.

var key2 = Key.Parse(Domain, str);
Assert.AreEqual(key, key2);

Key generators

Where keys come from?

Every entity is uniquely identified by key and Entity.Key property is immutable during whole entity’s lifecycle. The only moment when Entity.Key property can be set is the moment of entity construction. There are two scenarios and DataObjects.Net supports both:

1. Values of identity fields are provided by user code (not by framework). User code is responsible for passing identity values directly to entity constructor where these values are automatically transformed into key.

[HierarchyRoot]
[KeyGenerator(KeyGeneratorKind.None)]
public class Book : Entity
{
  [Field, Key]
  public string ISBN { get; private set; }

  // Accepts identity value (ISBN) and passes it to the base Entity constructor
  public Book(Session session, string isbn)
    : base(session, isbn)
  {
  }
}

2. Values of identity fields are provided by framework. This scenario might involve usage of database identity generators, tables with auto-increment column as well as any custom identity generators in order to produce new identity values for entity and build its key.

[HierarchyRoot]
public class Book : Entity
{
  [Field, Key]
  public int Id { get; private set; }

  public Book(Session session)
    : base(session)
  {
  }
}

While the first scenario is straighforward and doesn’t need any framework participation at all (except creating key from values passed to constructor), the second one is not so obvious because it requires framework to know how to obtain the next unique identity values for the specified persistent type. To solve this task the concept of key generator was introduced in DataObjects.Net.

Standard key generators

Key generator is a special object that knows all necessary information concerning generation of unique keys for particular hierarchy of persistent types, including:

  • details of identity generator implementation;
  • structure of key;
  • size of key cache, if generator supports caching;
  • mapping name, if generator is mapped to a particular database entity;
  • etc.

DataObjects.Net contains key generator implementations for the following types of identity fields:

  • byte, sbyte, short, ushort, int, uint, long, ulong;
  • string, Guid;

They can be used for persistent types with one identity field. This means they are not suitable for persistent types with complex key (more than one identity field). Sequential key generators (for integer types) also support caching.

Depending on features of the underlying storage, sequential key generator is mapped to sequence or a standalone table with one autoincrement column. Usually, the name of such physical entity is produced according to the rule ‘{type}-Generator’. For example, sequence for key generator of Int32 type will be ‘Int32-Generator’.

Shared key generators

In order to increase overall key generator performance and to widen persistent interfaces support single key generator is shared between all hierarchies it can serve. In other words, by default all hierarchies which have identical key structure are served by the same key generator.

[HierarchyRoot]
public class Book : Entity
{
  [Field, Key]
  public int Id { get; private set; }

  public Book(Session session)
    : base(session)
  {
  }
}

[HierarchyRoot]
public class Author : Entity
{
  [Field, Key]
  public int Id { get; private set; }

  public Author(Session session)
    : base(session)
  {
  }
}

Here we have 2 hierarchies with identical key structure (one field type of int) and no custom key generator is specified, so the default key generator is used. Having that, only 1 key generator (‘Int32-Generator’, if you look into database) will be created and it will serve both hierarchies at once. This means that sequence of Book identifiers as well as sequence Author identifiers will be shared. Say, one can get 1,2,3,6 for Book identifiers and 4,5,7,8,9 for Author identifiers. Nevertheless, all keys produced by this key generator are unique and can be used in any number of hierarchies which can be served by this key generator type without any restrictions.

Dedicated key generators

Dedicated key generators are the opposite case to shared generators. Having dedicated key generators, you get separate sequence for a given hierarchy.

[HierarchyRoot]
[KeyGenerator(KeyGeneratorKind.Default, Name = "BookGenerator")]
public class Book : Entity
{
  [Field, Key]
  public int Id { get; private set; }

  public Book(Session session)
    : base(session)
  {
  }
}

[HierarchyRoot]
[KeyGenerator(KeyGeneratorKind.Default, Name = "AuthorGenerator")]
public class Author : Entity
{
  [Field, Key]
  public int Id { get; private set; }

  public Author(Session session)
    : base(session)
  {
  }
}

In this example we indicate, we want to have 2 separate key generators: ‘BookGenerator’ and ‘AuthorGenerator’. DataObjects.Net will create them as two standalone sequences or tables with autoincrement field. Each hierarchy will be served by dedicated key generator with the corresponding name.

Hierarchies without key generator

In scenarios when key generator for a hierarchy is not required at all, this must be set up appropriately:

[HierarchyRoot]
[KeyGenerator(KeyGeneratorKind.None)]
public class Book : Entity
{
  [Field, Key]
  public string ISBN { get; private set; }

  // Accepts identity value (ISBN) and passes it to the base Entity constructor
  public Book(Session session, string isbn)
    : base(session, isbn) { }
}

Pay attention to KeyGeneratorAttribute usage together with identity values that are provided from the outside and passed to constructor.

Custom key generators

DataObjects.Net supports wide variety of keys and provides built-in key generator implementation only for keys that contain one identity field. For other scenarios that require key generator it is suggested to define custom key generator, inheriting from KeyGenerator type and implement a pair of its methods.

Here is the sample implementation of key generator for one-column Guid key:

[Service(typeof (KeyGenerator), Name = "MyGuidKeyGenerator")]
public class MyGuidKeyGenerator : KeyGenerator
{
  public override void Initialize(Domain domain, TupleDescriptor keyDescriptor)
  {
    // Key generator initialization logic, if any.
    // Executed on Domain.Build() step
  }

  public override Tuple GenerateKey(KeyInfo keyInfo, Session session)
  {
    // Creating tuple for key
    var keyTuple = Tuple.Create(keyInfo.TupleDescriptor);
    // Setting the first column of tuple with generated guid value
    keyTuple.SetValue(0, Guid.NewGuid());
    return keyTuple;
  }
}

The name that set in ServiceAttribute is uniquelly identifies custom key generator.

In order to indicate that a hierarchy must be served with custom key generator, use KeyGeneratorAttribute. Sample usage:

[HierarchyRoot]
[KeyGenerator(KeyGeneratorKind.Custom, Name = "MyGuidKeyGenerator")]
public class Author : Entity
{
  [Field, Key]
  public Guid Id { get; private set; }

  [Field]
  public EntitySet<Book> Books { get; private set; }
}

Custom generators are found by their names, so it is necessary to set the KeyGeneratorAttribute.Name property with correct name of key gerenator you defined earlier.